﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Common;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using Microsoft.ApplicationServer.Caching.AdminApi;
using System.Management.Automation.Runspaces;
using E4D.AppFabric.Entities.Engine;

namespace MDCAdminTool
{
	public class CacheCluster : PowerShellBase<object , CacheHost>
    {
        #region Fields
        private string        _Provider;
        private string        _ConnectionString;
        private bool          _IsOpen;
        private string        _ConfigFile;
        private string        _ImportConfigFile;
        private string        _ExportConfigFile;
        private Collection<CacheHost> _Hosts;
        #endregion

        #region Properties
        public string Provider
        {
            get { return _Provider; }
            set { _Provider = value; OnPropertyChanged( "Provider" ); }
        }
        public string ConnectionString
        {
            get { return _ConnectionString; }
            set { _ConnectionString = value; OnPropertyChanged( "ConnectionString" ); }
        }
        public bool IsOpen
        {
            get { return _IsOpen; }
            set { _IsOpen = value; OnPropertyChanged( "IsOpen" ); }
        }
        public string ConfigFile
        {
            get { return _ConfigFile; }
            set { _ConfigFile = value; OnPropertyChanged( "ConfigFile" ); }
        }
        public string ExportConfigFile
        {
            get { return _ExportConfigFile; }
            set { _ExportConfigFile = value; OnPropertyChanged( "ExportConfigFile" ); }
        }
        public string ImportConfigFile
        {
            get { return _ImportConfigFile; }
            set { _ImportConfigFile = value; OnPropertyChanged( "ImportConfigFile" ); }
        }

        public Collection<CacheHost> Hosts
        {
            get { return _Hosts; }
            set { _Hosts = value; OnPropertyChanged( "Hosts" ); }
        }

    	private Collection<Object> _groups = new Collection<Object>();

		public Collection<Object> Groups
		{
			get { return _groups; }
			set { _groups = value; OnPropertyChanged("Groups"); }
		}

		 
    	private HostGroup _hostGroup;

    	public HostGroup HostGroup
    	{
			get { return _hostGroup;  }
			set { 
				_hostGroup = value;
				OnPropertyChanged("HostGroup");
			}
    	}


        #endregion

        private DataCacheConfigSection dataCacheConfig;

        public CacheCluster()
        {
            Type  = CacheType.CacheCluster;
            Name  = "AppFabric";
            Hosts = new Collection<CacheHost>();
        }
        public CacheCluster( string configFile )
            : this()
        {
            InitCacheCluster( configFile );
        }

        #region Cache Cluster-related Commands

        /// <summary>
        /// Sets the context of your PowerShell session to the desired cache cluster.
        /// When starting a PowerShell session on a cache host, this command runs automatically, 
        /// by using the connection parameters specified in the cache host configuration file.
        /// </summary>
        /// <param name="name">The name of the cache cluster.</param>
        /// <param name="provider">The provider that is used to store the cluster configuration settings.</param>
        /// <param name="connectionString">The connection string to the database.</param>
        public void SetCacheCluster( string name , string provider , string connectionString )
        {
            Name             = name;
            Provider         = provider;
            ConnectionString = connectionString;

//            UseCacheCluster();
        }
        /// <summary>
        /// Sets the context of your PowerShell session to the desired cache cluster.
        /// When starting a PowerShell session on a cache host, this command runs automatically, 
        /// by using the connection parameters specified in the cache host configuration file.
        /// </summary>
        public void UseCacheCluster()
        {
            using (var session = SessionFactory.getLocalSession())
            {
                UseCacheCluster(session);
            }
        }

        /// <summary>
        /// Sets the context of your PowerShell session to the desired cache cluster.
        /// When starting a PowerShell session on a cache host, this command runs automatically, 
        /// by using the connection parameters specified in the cache host configuration file.
        /// </summary>
        public void UseCacheCluster(ISession session)
        {
            var p = new Dictionary<string, object>();

            if (!string.IsNullOrEmpty(ConnectionString))
            {
                p.Add("ConnectionString", ConnectionString);
                p.Add("Provider", Provider);
            }

            Invoke(session, "Use-CacheCluster", p);
        }


        public virtual String IconPath
        {
            get
            {
                if (String.IsNullOrEmpty(Provider) || Provider.ToLower() == "xml")
                {
                    return @"..\Resources\xml.png";
                }

                return @"..\Resources\sql.png";
            }
        }

		/// <summary>
        /// Starts all cache host services in the cluster. Lead hosts are started first.
        /// </summary>
        public override void Start()
        {
            using (var session = SessionFactory.getLocalSession())
            {
                Start(session);
            }
        }

        /// <summary>
        /// Starts all cache host services in the cluster. Lead hosts are started first. Executing command in a context of the given <see cref="runspace"/>
        /// </summary>
        public void Start(ISession session)
        {
            UseCacheCluster(session);
            Invoke(session, "Start-CacheCluster");
            LoadCacheClusterInfo();
            LoadCacheHosts(session);
        }

        /// <summary>
        /// Stops all cache host services in the cluster.
        /// </summary>
        public override void Stop()
        {
            using (var session = SessionFactory.getLocalSession())
            {
                Stop(session);
            }
        }

        /// <summary>
        /// Stops all cache host services in the cluster with given <see cref="runspace"/>
        /// </summary>
        public void Stop(ISession session, Boolean loadHosts = true)
        {
            UseCacheCluster(session);
            Invoke(session, "Stop-CacheCluster");
            if(loadHosts)
                LoadCacheHosts(session);
        }

        /// <summary>
        /// Restarts all cache host services in the cluster in the correct sequence.
        /// </summary>
        public override void Restart()
        {
            using (var session = SessionFactory.getLocalSession())
            {
                UseCacheCluster(session);
                Invoke(session, "Restart-CacheCluster");
                UseCacheCluster(session);
                LoadCacheClusterInfo();
                LoadCacheHosts(session);
            }
        }

        /// <summary>
        /// This command exports the cluster configuration settings, as they currently exist in the cluster, 
        /// to the specified XML-based configuration file. This command can be used regardless of 
        /// where you chose to store your cluster configuration settings (SQL Server or shared network folder).
        /// </summary>
        /// <param name="file">The fully qualified path and name for the XML-based configuration file. If a file already exists by this name, it will be overwritten.</param>
        public void ExportConfig()
        {
            var p = new Dictionary<string , object> { { "File" , ExportConfigFile } };
            using (var session = SessionFactory.getLocalSession())
            {
                UseCacheCluster(session);
                Invoke(session, "Export-CacheClusterConfig", p);
            }
        }

        /// <summary>
        /// This command imports the cluster configuration settings as described in the specified 
        /// XML-based configuration file. This command can be used regardless of where you chose to 
        /// store your cluster configuration settings (SQL Server or shared network folder).
        ///  You must restart the cluster with the Restart-CacheCluster for any changes to take effect.
        /// </summary>
        /// <param name="file">The fully qualified path and name of the XML-based configuration file that describes the cache cluster configuration settings to be applied to the cluster. </param>
        public void ImportConfig()
        {
            var p = new Dictionary<string , object> { { "File" , ImportConfigFile } };
            using (var session = SessionFactory.getLocalSession())
            {
                UseCacheCluster(session);
                Invoke(session, "Import-CacheClusterConfig", p);
            }
        }

        /// <summary>
        /// Load all cache host services that are members of the cache cluster.
        /// </summary>
        public void LoadCacheHosts()
        {
            using (var session = SessionFactory.getLocalSession())
            {
                UseCacheCluster(session);
                LoadCacheHosts(session);
            }
        }

		public Collection<MdcCache> Caches { get; set; }

        /// <summary>
        /// Load all cache host services that are members of the cache cluster.
        /// </summary>
        private void LoadCacheHosts(ISession session)
        {
        	Caches = null;

			Hosts = new Collection<CacheHost>();
            var r = Invoke(session, "Get-CacheHost");
            if (r == null) return;

            ServiceStatus status = ServiceStatus.Down;

            ObservableCollection<CacheHost> hosts1, hosts2 = new ObservableCollection<CacheHost>();
            hosts1 = ExtractHostInfo(r);
            foreach (var host in hosts1)
            {
                if (host.Status == ServiceStatus.Up || host.Status == ServiceStatus.Starting)
                    status = ServiceStatus.Up;
                
				Hosts.Add(host);
				hosts2.Add(host);
            }

            this.Status = status;

        	var hostGroup = new HostGroup() {Cluster = this};
            hostGroup.Hosts = hosts1;

        	var cacheGroup = new CacheGroup(){Cluster = this};
            var cacheGroupHA = new CacheGroupHA(){Cluster = this};

        	var caches = new Dictionary<String, MdcCache>();

			foreach (var cacheHost in hostGroup.Hosts)
        	{
        		foreach (var cache in cacheHost.CacheNames)
        		{
					var newCache = new MdcCache(cache);
					if(caches.ContainsKey(cache.Name))
					{
						newCache = caches[cache.Name];
					}

        			var host = new CacheHost(cacheHost.Name, cacheHost.CachePort, cacheHost.ServiceName, cacheHost.Status);
        			host.CacheNames = cacheHost.CacheNames;
        			host.Cluster = this;

        			newCache.Hosts.Add(host);
        			
					caches[cache.Name] = newCache;
        		}
        	}

        	foreach (var mdcCache in caches.Values)
        	{
                if (mdcCache.IsHighAvailability)
        		    cacheGroupHA.CacheNames.Add(mdcCache);
                else
                    cacheGroup.CacheNames.Add(mdcCache);
            }
			
			Groups = new Collection<object>();

			Groups.Add(hostGroup);
        	Groups.Add(cacheGroup);
            Groups.Add(cacheGroupHA);
            Hosts = hosts2;//ExtractHostInfo(r);
        }

        public override void LoadChildren()
        {
            LoadCacheHosts();
        }

        public void LoadChildren(ISession session)
        {
            LoadCacheHosts(session);
        }

        public void UpdateHostStatus(CacheHost host)
        {
            foreach (object obj in Groups)
            {
                Collection<MdcCache> caches;
                if (obj is CacheGroup)
                    caches = ((CacheGroup)obj).CacheNames;
                else if (obj is CacheGroupHA)
                    caches = ((CacheGroupHA)obj).CacheNames;
                else
                    caches = new Collection<MdcCache>();

                foreach (MdcCache cache in caches)
                {
                    foreach (CacheHost cacheHost in cache.Hosts)
                    {
                        if (cacheHost.Name == host.Name)
                            cacheHost.Status = host.Status;

                    }
                }
            }
        }

        /// <summary>
        /// Adds the specified host to the cluster
        /// </summary>
        /// <param name="hostName">host name</param>
        /// <param name="cachePort">cache port number</param>
        public void AddHost(string hostName, int cachePort)
        {
            try
            {
                using (var session = SessionFactory.getRemoteSesion(hostName))
                {
                    //var p = new Dictionary<string, object>();

                    //if (!string.IsNullOrEmpty(ConnectionString))
                    //{
                    //    p.Add("ConnectionString", ConnectionString);
                    //    p.Add("Provider", Provider);
                    //}

                    //Invoke(session.Runspace, "Use-CacheCluster", p);
                    //Invoke(session.Runspace, "Stop-CacheCluster");

                    AddCacheHost(session);
                    InvokeExpression(session, "netsh advfirewall firewall set rule group=\"Windows Server AppFabric: AppFabric Caching Service\" new enable=Yes");
                    InvokeExpression(session, "netsh advfirewall firewall set rule name=\"Remote Service Management (RPC)\" new enable=Yes");
                    InvokeExpression(session, "netsh advfirewall firewall set rule name=\"Remote Service Management (RPC-EPMAP)\" new enable=Yes");
                    InvokeExpression(session, "netsh advfirewall firewall set rule name=\"Remote Service Management (NP-In)\" new enable=Yes");

                    RegisterCacheHost(session);



                    //Invoke(session.Runspace, "Use-CacheCluster");
                    //Invoke(session.Runspace, "Start-CacheCluster");
                }

                UseCacheCluster();
                Start();
                //LoadCacheClusterInfo();
                //LoadCacheHosts();
            }
            catch (Exception ex)
            {
                Log(new ErrorMessage { Message = ex.Message });
            }
        }

        /// <summary>
        /// Removes the specified host to the cluster
        /// </summary>
        /// <param name="hostName">host name</param>
        /// <param name="cachePort">cache port number</param>
        public void RemoveHost(string hostName, int cachePort)
        {
            try
            {
                using (var session = SessionFactory.getRemoteSesion(hostName))
                {

                    UnregisterCacheHost(session);

                    //CacheHost should be stopped
                    //Lead host cannot be stopped
                    InvokeExpression(session, "net stop AppFabricCachingService");
                    RemoveCacheHost(session);
                }

                UseCacheCluster();
                Start();
            }
            catch (Exception ex)
            {
                Log(new ErrorMessage { Message = ex.Message });
            }
        }

        /// <summary>
        /// Unregisters the specified host to the cluster
        /// </summary>
        /// <param name="hostName">host name</param>
        /// <param name="cachePort">cache port number</param>
        public void UnregisterHost(string hostName, int cachePort)
        {
            try
            {
                using (var session = SessionFactory.getRemoteSesion(hostName))
                {
                    UnregisterCacheHost(session);
                }

            }
            catch (Exception ex)
            {
                Log(new ErrorMessage { Message = ex.Message });
            }
        }


        #endregion

        #region Helper Method
        private bool CheackConnectionStringAndProvider()
        {
            bool result = false;
            DbConnection conn = null;
            try
            {
                var factory = DbProviderFactories.GetFactory( Provider );
                conn = factory.CreateConnection();
                conn.ConnectionString = ConnectionString;
                conn.Open();
                result = true;
            }
            catch ( Exception ex )
            {
                Log( new ErrorMessage { Message =  ex.Message } );
                result = false;
            }
            finally
            {
                if ( conn != null )
                    conn.Dispose();
            }


            return result;

        }
        public void LoadCacheClusterInfo()
        {
            try
            {
                if ( File.Exists( ConfigFile ) )
                {
                    InitDataCacheConfigSection( ConfigFile );
                }
                //else
                //{
                //    Log( new ErrorMessage { Message =  "Can't find the config file." } );
                //}
            }
            catch ( Exception ex )
            {
                Log( new ErrorMessage { Message =  ex.Message } );
            }
        }
        private void LoadCacheClusterInfo( string configFile )
        {
            ConfigFile = configFile;
            LoadCacheClusterInfo();
        }
        private void InitDataCacheConfigSection( string file )
        {
            var config  = XElement.Load( file );
            XElement dcc = config.Descendants( "dataCacheConfig" ).FirstOrDefault();
            if ( dcc == null )
            {
                Log( new ErrorMessage { Message =  "Can't find the <dataCacheConfig> tag" } );
                return;
            }
            dataCacheConfig = new DataCacheConfigSection
            {
                ClusterName      = dcc.Attribute( "clusterName" ).Value ,
                HostName         = dcc.Attribute( "hostName" ).Value ,
                CacheHostName    = dcc.Attribute( "cacheHostName" ).Value ,
                Provider         = dcc.Element( "clusterConfig" ).Attribute( "provider" ).Value ,
                ConnectionString = dcc.Element( "clusterConfig" ).Attribute( "connectionString" ).Value
            };

            InitCacheCluster( dataCacheConfig );
        }
        public void InitCacheCluster( DataCacheConfigSection dccs )
        {
            Name             = dccs.ClusterName;
            Provider         = dccs.Provider;
            ConnectionString = dccs.ConnectionString;
        }
        public void InitCacheCluster( string configFile )
        {
            ConfigFile = configFile;
            try
            {
                if ( !string.IsNullOrEmpty( ConfigFile ) )
                {
                    // Step 1: Get Config Info.
                    LoadCacheClusterInfo();
                }

                // Step 2: Chooce Cache Cluster
                SetCacheCluster( Name , Provider , ConnectionString );

                // Step 3: Start Cache Cluster
                Start();

                // Must be last.
                //LoadCacheHosts();
            }
            catch ( Exception ex )
            {
                Log( new ErrorMessage { Message =  ex.Message } );
            }
        }

        protected void AddCacheHost(ISession session)
        {
            var p = new Dictionary<string, object>();
            if (!string.IsNullOrEmpty(ConnectionString))
            {
                p.Add("ConnectionString", ConnectionString);
                p.Add("Provider", Provider);
            }

            p.Add("Account", "NT Authority\\Network Service");
            Invoke(session, "Add-CacheHost", p);
        }

        protected void RemoveCacheHost(ISession session)
        {
            var p = new Dictionary<string, object>();
            Invoke(session, "Remove-CacheHost", p);
        }

        protected void RegisterCacheHost(ISession session)
        {
            var p = new Dictionary<string, object>();
            if (!string.IsNullOrEmpty(ConnectionString))
            {
                p.Add("ConnectionString", ConnectionString);
                p.Add("Provider", Provider);
            }
            Invoke(session, "Register-CacheHost", p);
        }

        private void UnregisterCacheHost(ISession session)
        {
            var p = new Dictionary<string, object>();
            if (!string.IsNullOrEmpty(ConnectionString))
            {
                p.Add("ConnectionString", ConnectionString);
                p.Add("Provider", Provider);
            }
            Invoke(session, "Unregister-CacheHost", p);
        }

        /// <summary>
        /// Starts the specified cache node or the entire cluster if it is down.
        /// </summary>
        /// <param name="session">remote runspace on the cache node</param>
        /// <param name="hostName">name of the cache node</param>
        /// <param name="cachePort">cache port of the cache node</param>
        protected void StartCacheHost(ISession session, string hostName, string cachePort)
        {
            //We should examine the cluster to find running hosts.
            //If there are running hosts we should use Start-CacheHost or Start-CacheCluster otherwise.
            var r = Invoke(session, "Get-CacheHost");
            if (r == null) return;

            var hosts = ExtractHostInfo(r);
            int activeHosts = (from x in hosts
                         where x.Status == ServiceStatus.Up
                         select x).Count();

            if (activeHosts > 0)
            {
                Invoke(session, "Start-CacheCluster", new Dictionary<string, object>() { { "Hostname", hostName }, { "CachePort", cachePort } });
            }
            else
            {
                Invoke(session, "Start-CacheCluster");
            }
        }

        private ObservableCollection<CacheHost> ExtractHostInfo(Collection<System.Management.Automation.PSObject> r)
        {
            var hosts = new ObservableCollection<CacheHost>();

            foreach (var host in r)
            {
                if (host.BaseObject is HostInfo)
                {
                    var h = host.BaseObject as HostInfo;
                    hosts.Add(new CacheHost(h, this));
                }
            }
            return hosts;
        }

        #endregion

        #region Not Implemented
        public override CacheStats LoadStatistics()
        {
            throw new NotImplementedException();
        }
        public override void LoadConfig()
        {
            throw new NotImplementedException();
        }
        public override void SaveConfig()
        {
            throw new NotImplementedException();
        }
        public override void Add( CacheHost name )
        {
            throw new NotImplementedException();
        }
        public override void Remove( CacheHost name )
        {
            throw new NotImplementedException();
        }
        public override void Add( CacheEntityBase item )
        {
            throw new NotImplementedException();
        }
        public override void Remove( CacheEntityBase item )
        {
            throw new NotImplementedException();
        }
        #endregion

        public IEnumerable<String> GetAllowedAccounts()
        {
            using (var session = SessionFactory.getLocalSession())
            {
                UseCacheCluster(session);
                var r = Invoke(session, "Get-CacheAllowedClientAccounts");
                return r.Select(x => x.BaseObject).OfType<String>();
            }
        }

        public void GrantAllowedAccount(String account)
        {
            using (var session = SessionFactory.getLocalSession())
            {
                UseCacheCluster(session);
                Invoke(session, "Grant-CacheAllowedClientAccount", new Dictionary<String, Object> { { "Account", account } });
            }
        }

        public void RevokeAllowedAccount(String account)
        {
            using (var session = SessionFactory.getLocalSession())
            {
                UseCacheCluster(session);
                Invoke(session, "Revoke-CacheAllowedClientAccount", new Dictionary<String, Object> { { "Account", account } });
            }
        }

        public void GetHealthStatus()
        {
            using (var session = SessionFactory.getLocalSession())
            {
                Invoke(session, "Get-CacheClusterHealth");
            }
        }
    }
}
